//
//  Bag.swift
//  DataStructures
//
//  Created by Red App on 2018/6/13.
//  Copyright © 2018年 CEC-CESEC. All rights reserved.
//

import Foundation

////Bag 结构是一个泛型结构，需要元素类型必须是 Hashable 的。Hashable 允许你对元素进行比较，在 0（1）时间复杂度上只存储唯一值。也就是说，无论内容有多复杂，Bag 存取速度相同。你通过定义一个结构体，强制让它具备值语义（C++ 术语），这就和 Swift 标准库保持一致了。
//这是 Bag 的基本属性：
//
//用一个作为内部存储结构。因为字典的键是唯一的，你可以用它来存储数据。字典的值则表示每个元素的个数。fileprivate 关键字表示这个属性是隐藏的，在外部不可访问。
//uniqueCount 返回了字典的每一种对象的统计值，并不累加它们每一个的数量。例如，一个 Bag 中有 4 个橙子和 2 个苹果只会返回 2。
//totalCount 返回的是 Bag 中所有对象的总计。以同一个例子为例，totalCount 返回值为 6。


struct Bag<Element: Hashable> {
    // 1
    fileprivate var contents: [Element: Int] = [:]
    
    // 2
    var uniqueCount: Int {
        return contents.count
    }
    
    // 3
    var totalCount: Int {
        return contents.values.reduce(0) { $0 + $1 }
    }
    //首先，创建一个空的 init 方法。在定义了其他初始化方法之后，你必须添加这个方法，否则编译器会报错。
    init() { }
    
    // 然后，定义一个初始化方法并接受一个元素为 Sequence 集合的参数。sequence 参数的类型必须和元素类型匹配。例如，Array 和 Set 对象。然后，对 sequence 进行迭代，挨个添加元素。
    init<S: Sequence>(_ sequence: S) where S.Iterator.Element == Element {
        for element in sequence {
            add(element)
        }
    }
    
    // 最后一个方法类似，但元素类型变成元素了(Element, Int)。这种情况最典型的例子就是字典。 这里，你依然对 sequence 中的元素进行迭代并以指定的个数来添加元素。
    init<S: Sequence>(_ sequence: S) where S.Iterator.Element == (key: Element, value: Int) {
        for (element, count) in sequence {
            add(element, occurrences: count)
        }
    }
    
    
    /// add(_:occurrences:) 方法提供了增加元素的方法。它需要两个参数：泛型参数 Element 和一个 Optional 的元素个数。如果 Bag 实例以常量形式 let 定义而不是变量 var 形式定义的话，则这个方法无效。
//    precondition(_:_:)方法的第一个参数是一个 Boolean 表达式，如果为 false，则程序会中断，并在 Debug 窗口输出第二个参数的内容。这个方法有一个前置条件，以保证 Bag 能够被正确使用。这个方法检查了调用 add 方法时符合我们的预设。
//    if 语句判断元素是否已经存在，如果存在，则累加它的计数器，否则加入新的元素。
    ///
    /// - Parameters:
    ///   - member: <#member description#>
    ///   - occurrences: <#occurrences description#>
    mutating func add(_ member: Element, occurrences: Int = 1) {
        // 2
        precondition(occurrences > 0, "Can only add a positive number of occurrences")
        
        // 3
        if let currentCount = contents[member] {
            contents[member] = currentCount + occurrences
        } else {
            contents[member] = occurrences
        }
    }
    
    
    /// remove(_:occurrences:) 方法使用的参数和 add 方法一模一样，只不过做了相反的事情：
    
//    首先判断元素是否存在，确保在删除它时起码有足够的数目能够被删除。
//    然后保证元素个数大于 0.
//    最后，如果元素存在则将元素个数减少。如果元素个数减少后小于等于 0，直接删除整个元素。
    ///
    /// - Parameters:
    ///   - member: <#member description#>
    ///   - occurrences: <#occurrences description#>
    mutating func remove(_ member: Element, occurrences: Int = 1) {
        // 1
        guard let currentCount = contents[member], currentCount >= occurrences else {
            preconditionFailure("Removed non-existent elements")
        }
        
        // 2
        precondition(occurrences > 0, "Can only remove a positive number of occurrences")
        
        // 3
        if currentCount > occurrences {
            contents[member] = currentCount - occurrences
        } else {
            contents.removeValue(forKey: member)
        }
    }
}

//你可以用 Swift 标准库中的一个协议来解决这个问题。在 shoppingCart 变量上面的 Bag 类型定义结束的 } 之后添加
//采用 CustomStringConvertible 协议需要实现一个属性，叫做 description。这个属性返回一个实例对象的文字表示。
//
//在这里，你可以放入任何足以表示你的数据的逻辑。因为字典已经继承了这个协议，你可以简单调用 contents 对象的 description 值。
extension Bag: CustomStringConvertible {
    var description: String {
        return String(describing: contents)
    }
}


extension Bag: ExpressibleByArrayLiteral {
    typealias ArrayLiteralElement = Element
    
    init(arrayLiteral elements: Bag.ArrayLiteralElement...) {
        self.init(elements)
    }
}

extension Bag: ExpressibleByDictionaryLiteral {
    typealias Key = Element

    typealias Value = Int
    
    init(dictionaryLiteral elements: (Bag.Key, Bag.Value)...) {
        self.init(elements.map {(key: $0.0, value: $0.1)})
//        self.init(dictionaryLiteral: (Bag.Key, Bag.Value))
//        self.init(dictionaryLiteral: (Hashable, Bag.Value))
//        self.init(dictionaryLiteral: (Bag<Element>.Key, Bag<Element>.Value))
    }
}

// 就像数组和字典一样，你可以遍历一个 Bag 对象。因为 Bag 还没有实现 Sequence 协议，编译不能通过。
//在 ExpressibleByDictionaryLiteral 扩展后加入另一个扩展:
extension Bag: Sequence {
    typealias Iterator = DictionaryIterator<Element, Int>
    func makeIterator() -> DictionaryIterator<Element, Int> {
        return contents.makeIterator()
    }
}
//定义了一个类型别名，叫做 Iterator，这是 Sequence 中指定的，需要实现 IteratorProtocol 协议。DictionaryIterator 类型是字典用于迭代其元素的。你可以用它，因为 Bag 在底层使用了字典来存储数据的。
//makeIterator() 方法返回一个 Iterator，它会用来对序列中的每个元素进行迭代。
//调用 contents 的 makeIterator() 方法创建一个 Iterator，它已经实现了 Sequence 协议。

























